package com.esdk.sql.orm;

import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.esdk.esdk;
import com.esdk.exception.SdkRuntimeException;
import com.esdk.sql.*;
import com.esdk.utils.CharAppender;
import com.esdk.utils.Constant;
import com.esdk.utils.EasyStr;
import com.esdk.utils.JsonUtils;

import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.util.*;
import java.util.Map.Entry;
import java.util.function.Consumer;

abstract public class ParentResultSet<T extends ParentRow> extends ABResultSet<T> {
	abstract public T createRowInstance();
	protected Class _rowCls;//第一条记录或到最后才通过反射获取，以减少反射的性能问题
  protected HashMap<String,HashMap<Integer,Integer>> uniqueMap;
  private boolean isChanged=false;
	private String primaryKeyName=null;
  
	public ParentResultSet(ResultSet resultSet) {
		super(resultSet);
	}

	@Override
	protected T getCurrent() throws SQLException {
		T result = createRowInstance();
		result.setConnection(rs.getStatement().getConnection());
		result.record=super.getCurrentMap();
		result.columns=getColumns();
		result.isExistRecord = true;
		if(_rowCls==null)
			_rowCls=result.getClass();
		return result;
	}

	public int indexOf(T row) {
		int index = rowList.indexOf(row);
		if (index >= 0)
			return index;
		else {
			for (int i = 0, n = this.size(); i < n; i++) {
				if (this.getRow(i).getPrimaryKey().equals(row.getPrimaryKey()))
					return i;
			}
			return -1;
		}
	}

	@Override
	public T getRow(int index) {
		int curPos = this.cursorPos;
		try {
			if(absolute(index)) {
				IRow result = this.getCurrentRow();
				this.cursorPos = curPos;
				return (T) result;
			}else {
				return null;
			}
		} catch (SQLException e) {
			this.cursorPos = curPos;
			throw new SQLRuntimeException(e);
		}
	}
	
  public T getFirstRow(boolean isCreateInstance) {
  	T result=(T)getRow(0);
  	if(result==null&&isCreateInstance) {
  		result=(T)createRowInstance();
			RowUtils.fillDefaultVals(result);
  	}
  	return result;
  }
  
	protected Object gainCurrentRow(ResultSet rset) throws SQLException {
		esdk.tool.read(rset);
		return rowList.get(cursorPos);
	}

	@Override
	public String[] getColumns() {
		if (columns == null) {
			if(rowList.size()>0)
				columns = rowList.get(0).getColumns();
			else
				columns=(String[])esdk.reflect.getFieldValue(esdk.reflect.getFieldValue(this,"md"),"FieldNames");
		}
		return columns;
	}

	public String getPrimaryKeyName(){
		if(primaryKeyName==null){
			if(rowList.size()>0)
				primaryKeyName=getFirstRow().getPrimaryKeyName();
			else
				primaryKeyName=(String)esdk.reflect.getFieldValue(esdk.reflect.getFieldValue(this,"md"),"PrimaryKey");
		}
		return primaryKeyName;
	}

	@Override public JSONArray toJsonArray(boolean isFormatJavaBeanName){
  	try {
  		return JsonUtils.toJsonArray(this.getAllRows(),JsonUtils.IResultSetPropertyPreFilter);
		} catch (SQLException e) {
			throw new SQLRuntimeException(e);
		}
  }
  
  public JSONArray toJsonArrayWithExtendFields(){
  	try {
  		List<T> list=this.getAllRows();
  		JSONArray result=new JSONArray();
  		for(T item:list) {
  			JSONObject jo=item.toJsonObject(true,true);
  			result.add(jo);
  		}
  		return result;
		} catch (SQLException e) {
			throw new SQLRuntimeException(e);
		}
  }

	@Override public String toString(){
//		return toJsonArray().toString();
		return super.toString();
	}

	public JSONArray toJsonArray(final String... columns) {
		String[] excludeColumns = EasyStr.remove(getColumns(), columns);
		for (int i = 0; i < excludeColumns.length; i++) {
			excludeColumns[i] = EasyStr.toCamelCase(excludeColumns[i]);
		}
		excludeColumns=esdk.str.toArray(excludeColumns,RowUtils.Keywords);
		try {
			return JsonUtils.toJsonArray(this.getAllRows(),JsonUtils.getPropertyPreFilter(excludeColumns));
		} catch (SQLException e) {
			throw new SQLRuntimeException(e);
		}
	}

	@Override public JSONArray toJsonArray(int start, int limit) {
		try {
			return JsonUtils.toJsonArray(this.getSubList(start, limit),JsonUtils.IResultSetPropertyPreFilter);
		} catch (SQLException e) {
			throw new SQLRuntimeException(e);
		}
	}

	@Override
	public ParentResultSet<T> sort(boolean isDesc, String... cols) {
		return (ParentResultSet) super.sort(isDesc, cols);
	}

	@Override
	public ParentResultSet<T> sort(String... columnKeys) {
		return (ParentResultSet) super.sort(columnKeys);
	}

	@Override
	public ParentResultSet<T> sort(OrderBy... cols) {
		return (ParentResultSet) super.sort(cols);
	}

	public List<T> toBeanList(Class<T> beanCls) {
		return toBeanList(beanCls, 0, size());
	}

	public List<T> toBeanList(Class<T> beanCls, int start, int limit) {
		try {
			String beanClassName = beanCls.getName();
			beanClassName = beanClassName.substring(0, beanClassName.length() - 9).concat("Bean");
			Class cls = Thread.currentThread().getContextClassLoader().loadClass(beanClassName);
			List result = new ArrayList<List>();
			List rowList = getSubList(start, limit);
			for (Iterator iter = rowList.iterator(); iter.hasNext();) {
				IRow row = (IRow) iter.next();
				Object pojo = esdk.reflect.safeNewInstance(cls);
				result.add(RowUtils.loadFrom(pojo, row, false));
			}
			return result;
		} catch (Exception e) {
			throw esdk.tool.wrapThrowble(e);
		}
	}
	
	@Override protected ParentResultSet<T> clone() throws CloneNotSupportedException{
		try{
			java.lang.reflect.Constructor constructor=this.getClass().getConstructor(new Class[]{ResultSet.class});
			ParentResultSet result=(ParentResultSet)constructor.newInstance(new Object[]{rs});
			result.rowList=this.rowList;
			result.rsmd=this.rsmd;
			result.mapColumnClass=this.mapColumnClass;
			result.columns=this.columns;
			return result;
		}catch(Exception e){
				throw esdk.tool.wrapThrowble(e);
		}
	}
	
	@Override void addRowList(IRow currentRow) throws SQLException{
		super.addRowList(currentRow);
	}
	
  /**按主键查询记录*/
	public T findById(Object pkid){
		return findByUnique(false,getPrimaryKeyName(),pkid);
	}

	/**通过创建索引快速获取记录，只接受等于条件表达式，不能使用大于、小于、in、不等于等表达式
	 * 首次搜索会先创建索引，第二次再搜索就会非常快
	 * */
	public T findByUnique(boolean autoCreateInstance,Object...args) {
		T result=null;
		Map<String,Object> map=findEqConditions(args);
		String[] sortedCols=esdk.array.toArray(map.keySet());
		String columns=esdk.str.valueOf(sortedCols);
		if(uniqueMap==null || isChanged || !uniqueMap.containsKey(columns)) {
			createIndex(sortedCols);
			isChanged=false;
		}
  	if(uniqueMap!=null) {
  		Integer rowindex=getUniqueMap(columns).get(esdk.str.valueOf(map.values()).hashCode());
  		if(rowindex!=null)
  			result=this.getRow(rowindex);
  	}
  	if(result==null && autoCreateInstance) {
  		result=(T)createRowInstance();;
  		result.load(map);
  	}
  	return result; 
	}
	
	/**生成唯一值索引，注意如果记录被更新，必须手工重新执行，否则会出错误的搜索结果*/
	private Map<Integer,Integer> getUniqueMap(String columnsStr) {
		HashMap<Integer,Integer> result=null;
		if(uniqueMap==null)
			uniqueMap=new HashMap();
		if(!uniqueMap.containsKey(columnsStr)) {
			result=new HashMap<Integer,Integer>();
			uniqueMap.put(columnsStr,result);
		}else
			result=uniqueMap.get(columnsStr);
		return result;
	}
	
  /**
   * 生成索引值，用于快速定位
   * 注意：对字段名先排序，以确保用户输入顺序不受限制，但如果字段数量不一致，仍然无法正确使用该索引。
   * */
	public ParentResultSet<T> createIndex(String[] columns){
		String[] sortedCols=esdk.array.sortByHash(columns);
		String sortedColumnsStr=esdk.str.join(sortedCols);
		for(int i=0,n=this.size();i<n;i++){
			T row=this.getRow(i);
			CharAppender sb=new CharAppender(',');
			for(String col:sortedCols) {
				sb.append(row.getString(col));
			}
			getUniqueMap(sortedColumnsStr).put(sb.toString().hashCode(),i);
		}
		return this;
	}	
	
	/**找出等于条件的值，用于自动赋值*/
	private Map<String,Object> findEqConditions(Object... args){
		HashMap<String,Object> result=new HashMap();
		String fieldValue=null,expression=Expression.EQ;
		Object rightValue=null;
		boolean isFieldValue=true;
		for(Object arg:args) {
			if(isFieldValue) {
				fieldValue=(String)arg;
				isFieldValue=false;
			}else if(esdk.array.contains(Expression.ALL,arg)) {
				expression=(String)arg;
			}else {
				rightValue=arg;
				isFieldValue=true;
				if(expression==Expression.EQ) {
					result.put(fieldValue,rightValue);
					if(rightValue.getClass().equals(Date.class))
						result.put(fieldValue,new Timestamp(((Date)rightValue).getTime()));
				}
			}
		}
		return result;
	}
	
  
	/**left join操作，类似于select的left join处理逻辑，适合处理1对1或1对N
	 * appendColumns：增加字段如果为空则获取记录集的所有字段（注意会排除重复字段）
	 * onFieldsMap：字段匹配条件，只能支持字段值匹配，不能指定常量值匹配，如有常量值条件应先执行过滤条件处理。
	 * 							key为原记录集的字段名，value为join记录集的字段名。
	 *              为null时会以两个记录集的重叠字段（去掉公用字段和主键）做为匹配条件。
	 * */
	public <PRS extends ParentResultSet> PRS join(IRowSet rs,String[] appendColumns,Map<String,String> onFieldsMap){
		if(rs==null || rs.size()==0)
			return (PRS)this;
		if(esdk.obj.isEmpty(appendColumns)) {
			appendColumns=rs.getColumns();
		}
		appendColumns=esdk.str.remove(appendColumns,this.getColumns());
		columns=esdk.str.toArray(columns,appendColumns);
		if(onFieldsMap==null || onFieldsMap.keySet().size()==0) {
			String[] joinFields=esdk.array.overlap(this.getColumns(),rs.getColumns());
			joinFields=esdk.str.remove(esdk.str.remove(joinFields,Constant.SystemFields),"id");
			onFieldsMap=new LinkedHashMap();
			for(String field:joinFields) {
				onFieldsMap.put(field,field);
			}
		}
		for(IRow row:this) {
			RowExpressions exps=new RowExpressions();
			for(Entry<String,String> entry:onFieldsMap.entrySet()) {
				exps.add(RowExpression.create(null,entry.getValue(),row.get(entry.getKey())));
			}
			IRow findRow=rs.filter(1,exps).getFirstRow();
			if(findRow!=null)
				((ABRow)row).load(findRow.toMap(false,appendColumns));
		}
		return (PRS)this;
	}
	
	@Override
	public ParentResultSet<T> update(Consumer<T> fn) throws SQLException{
		return (ParentResultSet)super.update(fn);
	}

	public ParentResultSet<T> setPrimaryKeyName(String primaryKeyName){
		this.primaryKeyName=primaryKeyName;
		return this;
	}

	@Override public void close(){
		super.close();
		if(_rowCls==null)
			_rowCls=esdk.reflect.loadClass(this.getClass().getName().replace("ResultSet","Row"));
	}
}
